package com.swe727.spring2011.constraint1.client;

import java.rmi.AlreadyBoundException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Random;

import com.swe727.spring2011.constraint1.coordinate.Coordinate;
import com.swe727.spring2011.constraint1.node.Node;
import com.swe727.spring2011.constraint1.node.NodeRegistration;
import com.swe727.spring2011.constraint1.observation.Observation;
import com.swe727.spring2011.constraint1.observation.ObservationType;
import com.swe727.spring2011.constraint1.server.*;
import com.swe727.spring2011.constraint1.client.util.*;
import java.util.*;
import java.io.*;

public class NodeController {

	private Coordinate currentLocation;
	private final Grid GRID;
	private final int MAX_MOVES;
	private final String NODE_ID;
	private final String RMI_ADDRESS;
	private final int RMI_PORT;
	private final int RESTSECONDS;
	private final int INSTRUMENT_THIS_NODE;
	private final String EXPERIMENT_LOGFILE;
	private boolean IN_STEADY_STATE;
	
	//System.out.println("<name>,<number of obs>,<seconds between><server address><server port>\n");
	public NodeController(String nodeId, 
			int observationCount, 
			int gridSize, 
			int restseconds,
			int nodesPerZone, //used only for log file nameing
			String rmiRegistryAddress, 
			int rmiRegistryPort,
			int instrumentThisNode) throws RemoteException, AlreadyBoundException, NotBoundException {
		
		IN_STEADY_STATE=false;
		EXPERIMENT_LOGFILE = nodeId+"."+observationCount+"."+gridSize+"."+restseconds+"."+nodesPerZone+".log";
		
		INSTRUMENT_THIS_NODE = instrumentThisNode; //1=yes default=0   do not instrument
		Sysout("instument = "+INSTRUMENT_THIS_NODE);
		
		MAX_MOVES = observationCount;
		GRID = new Grid(gridSize, gridSize);
		NODE_ID = nodeId;
		RMI_ADDRESS = rmiRegistryAddress;
		RMI_PORT = rmiRegistryPort;
		RESTSECONDS = restseconds;
		
		//delete the experiment log file if it exists.
		File f = new File(EXPERIMENT_LOGFILE);
		if (f.exists())
			f.delete();
		this.WriteLog("node start");
		
		Registry registry = LocateRegistry.getRegistry(rmiRegistryAddress, rmiRegistryPort);

		// register with the zone server
		RegisterNodeRemote registration = (RegisterNodeRemote) registry.lookup(ZoneServer.REGISTER_ID);
		registration.registerNode(new NodeRegistration(new Node(NODE_ID, rmiRegistryAddress, rmiRegistryPort)));
		this.WriteLog("did register node");

		//handle exception. this prevents needed to take down server each test run
		try {
			ClientMedevacHandler handler = new ClientMedevacHandler(this);
			ClientMedevacRemote stub = (ClientMedevacRemote) UnicastRemoteObject.exportObject(handler, 0);
	        registry.bind(nodeId, stub);
		} catch (java.rmi.AlreadyBoundException ex) {
			//do nothing
		}
        
        
        // calculate random start point
        Random random = new Random(System.currentTimeMillis());
        int x = random.nextInt(gridSize);
        int y = random.nextInt(gridSize);
        currentLocation = new Coordinate(x, y);
        startSimulation();
	}
	
	private void startSimulation() throws RemoteException, NotBoundException {
		int i = 0;
		long beforeObservationTicks = 0;
		long afterObservationTicks = 0;
		long observationTicksCumulative = 0;
		int observationCallCount = 0;
		int observationCount = 0;
		
		ObservationSimulator observationSimulator = new ObservationSimulator();
		ArrayList<Observation> ot=null;
		
		System.out.println("Making "+MAX_MOVES);
		while (i++ < MAX_MOVES) {
			// evenly pick one for now, Scott is finishing this piece
			Sysout("Getting obs for move "+i);
			
			if (i>=5 && i<=15)
				IN_STEADY_STATE = true;
			else
				IN_STEADY_STATE = false;
			
			if (INSTRUMENT_THIS_NODE>0) {
				if (i >= 5 && i<= 15) {  //between observation 5 and 14 inclusive. steady state
					beforeObservationTicks = Calendar.getInstance().getTimeInMillis();
					Sysout("before obs tick="+beforeObservationTicks);
					
					ot = observationSimulator.getObservation(currentLocation);	
										
					observationCount += ot.size();  //cumulate observation count
					
					//log nodeid, timestamp, ot.size, coordinate
					
					afterObservationTicks = Calendar.getInstance().getTimeInMillis();
					Sysout("after obs tick="+afterObservationTicks);
					
					//Sysout("aft ticks:"+afterObservationTicks+" before ticks:"+beforeObservationTicks);
					observationTicksCumulative += (afterObservationTicks - beforeObservationTicks);
					
					Sysout("obs tick cum="+observationTicksCumulative);
					
					observationCallCount++;
					
				}
				else {
					ot = observationSimulator.getObservation(currentLocation);
					observationCount += ot.size();
				}
			} else {
				ot = observationSimulator.getObservation(currentLocation);
				observationCount += ot.size();
			}
				

			/*
			ObservationType[] observationTypes = ObservationType.values();
			Random random = new Random(System.currentTimeMillis());
			ObservationType type = observationTypes[random.nextInt(observationTypes.length)];
			*/
			
			
			Registry registry = LocateRegistry.getRegistry(RMI_ADDRESS, RMI_PORT);
			SaveObservationRemote save = (SaveObservationRemote) registry.lookup(ZoneServer.SAVE_OBSERVATION_ID);
			ServerMedevacRemote medvac = (ServerMedevacRemote) registry.lookup(ZoneServer.MEDEVAC_ID);

			
			if (IN_STEADY_STATE)
				save.logMemoryFootprintOfServer();

			if (IN_STEADY_STATE)
				this.logMemoryFootprint();
			
			
			for (int j=0;j<ot.size();j++) {

				Observation obs = ot.get(j);

				//log node,action,time,coordinate
				this.WriteLog("sending obs for "+currentLocation.getX()+"."+currentLocation.getY());
				save.saveObservation(obs);

				Sysout("Reporting observation: " + obs.getData());
				
				if (obs.getData() == ObservationType.MEDEVAC) {
					this.WriteLog("send medevac - coord = "+currentLocation.getX()+"."+currentLocation.getY());
					medvac.requestMedevac(currentLocation);
				}
				
			}
			
			currentLocation = GRID.getNextMove(currentLocation);
			
			System.out.println(currentLocation);
			
			try {
				
				
				LogNormalDistribution logNormalObj = new LogNormalDistribution(RESTSECONDS);
				long sleeptime = (long)(1000 * logNormalObj.nextLogNormal());
				
				Thread.sleep(sleeptime);
				
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} //end while
		
		System.out.println("Finished making moves");
		
		if (INSTRUMENT_THIS_NODE>0) {
			Sysout("observationTicksCumulative="+observationTicksCumulative);
			Sysout("observationCallCount="+observationCallCount);
			Sysout(this.NODE_ID+" Average obs latency (m1) = "+observationTicksCumulative / observationCallCount/1000+" seconds.");
			float flt = (float) observationTicksCumulative / (float)observationCallCount;
			flt = flt /1000;
			this.WriteLog("Average obs latency (m1) = "+flt+" seconds.");
			this.WriteLog("got "+observationCount+" observations");
			
		}
			
		System.exit(0);
		
	}
	private void logMemoryFootprint() {
		long memused = Runtime.getRuntime().totalMemory();
		this.WriteLog("memory used = "+memused);
	}
	
	private void WriteLog(String msg) {
	
		try
		{
			String logString = NODE_ID+","+NodeController.TimeStamp()+","+msg+"\n";
			FileOutputStream fos = new FileOutputStream(EXPERIMENT_LOGFILE, true);
			
			fos.write(logString.getBytes());
			
			fos.close();
		}
		catch(FileNotFoundException ex)
		{
			System.out.println("FileNotFoundException : " + ex);
		}
		catch(IOException ioe)
		{
			System.out.println("IOException : " + ioe);
		}
	}
	private void Sysout(String msg) {
		System.out.println(NODE_ID+":"+msg);
	}
	public void ReceivedMedevace(Coordinate coordinate) {
		Sysout("Medevac Handler is notifying nodecontroller for coord:"+coordinate.getX()+","+coordinate.getY());
		this.WriteLog("got medevac - coord = "+coordinate.getX()+"."+coordinate.getY());
	}
	//=======statics
	public static String TimeStamp() {
		Calendar ca = Calendar.getInstance();
		String time = String.valueOf(ca.get(Calendar.HOUR_OF_DAY))+":"+
		String.valueOf(ca.get(Calendar.MINUTE))+":"+
		String.valueOf(ca.get(Calendar.SECOND))+":"+
		String.valueOf(ca.get(Calendar.MILLISECOND));
		return time;
	}
	public static void main(String[] args) throws RemoteException, AlreadyBoundException, NotBoundException {
		String echoline ="args=";
		for (int i = 0;i<args.length;i++) {
			if (i==args.length-1)
				echoline = echoline+args[i];
			else
				echoline = echoline+args[i]+",";
		}
		if (args.length != 7 && args.length !=8) {
			System.out.println(echoline);
			System.out.println("<name>,<number of obs>,<grid size>,<seconds between>,<nodes per zone>,<server address><server port>,<instrument this node>\n");
		}
		else {
			int intrumentThisNode = 0;
			System.out.println("args length = "+args.length);
			if (args.length == 8)
				intrumentThisNode = Integer.parseInt(args[7]);
			
			new NodeController(args[0],Integer.parseInt(args[1]),Integer.parseInt(args[2]),Integer.parseInt(args[3]),Integer.parseInt(args[4]),args[5],Integer.parseInt(args[6]),intrumentThisNode);
		}
		
	}
}
